#Copyright 2009-2010 by Ward, David J ( pymethius@ominian.net )
from logging import getLogger
log = getLogger(__name__)

from hashlib import sha1

from twisted.cred.error import LoginFailed, UnauthorizedLogin, LoginDenied
from twisted.internet import defer
from twisted.enterprise.adbapi import ConnectionPool


from webmud.lib import config
from webmud.util import IoC

from registry import Registry
from routeparser import RoutesFactory
from responsehandler import ResponseHandler

class NormalUser(object):
    

    def __init__(self, profile, protocol = None):
        self.name = profile.pop("name")
        self.fullname = profile.pop("name_full")
        self.userType = profile.pop("type")
        self.protcol = protocol
        
        for k in profile.keys():
            setattr(self, k, profile[k])
        
    """
        The following setters should be used for re-login events, not for construction
    """
    def setProtocol(self, protocol): self.protocol = protocol
    def setUserType(self, userType): self.userType = userType
    def setfullname(self, fullname): self.fullname = fullname
    def setRoutes(self, routes): self.handleRequest = routes
    """
        Used predominantly for security checks
    """
    def __eq__(self, other):
        if self.name != other.name: return False
        if self.userType != other.userType: return False
        return True
        

    
class AdminUser(NormalUser):
    pass

class Observable(object):
    """ A simple mixin like class to make events in subclasses 
    """
    pass

class Realm(object):
    
    def __init__(self, dbpool):
        self.userConfig = self._config()
        self.routes = RoutesFactory(dbpool) #Provides the playbook of what routes go to what type of user        
        self.registry = Registry() #Just like a hostel register, users sign up and get registered for the short-medium term
        self.dbpool = dbpool        
     
       
        
    def _config(self):
        return config.require("realm") 
        
    def _ConstructReturnUser(self, cls, profile):
        log.debug("Checking registry for user before build with profile data %s" % profile)
        if self.registry.hasByName(profile["name"]):
            userObj = self.registry.get(profile["name"])
            
            self.registry.register(profile["name"], userObj)
            return userObj
        else:
            log.debug("Creating new user(%s) w profile %s " % (cls, profile,))
            """User is new so build it up from scratch"""
            userObj = cls(profile)
            userObj.setRoutes(self.routes.build(userObj.userType))
            self.registry.register(userObj.name, userObj )
            
            return userObj
            
        
    def getAnonymous(self, identifier, protocol = None):
        profile = {}
        profile["name_full"] = "AnonymousUser%s" % identifier
        profile["name"] = profile["name_full"]
        profile["type"] = "AnonymousUser"
        
        userObj = self._ConstructReturnUser(NormalUser, profile )
        if protocol: #Used for out of cycle creations
            userObj.setProtocol(protocol)
        return userObj
   
    def getAdminUser(self, profile):
        return self._ConstructReturnUser(AdminUser, profile )
                
    def getNormalUser(self, profile):
        return self._ConstructReturnUser(NormalUser, profile )
        
    
    def login(self, username, protocol,  hash = None, password=None, salt=None, key=None, cookie = None):
        """
           My solution to method overloading:
            Required Param: userName
            For clear text logins - password
            For sha1 w/salt - hash, salt
            For keybased login - key
            for cookie login 
        """
        
        d = self.dbpool.runInteraction(self.mappedResultQuery, "SELECT * FROM users WHERE name = ? LIMIT 1", (username,) )        
        d.addErrback(self._loginFailure)
        
        if hash and salt:
            log.debug("login handled by _loginByHash")
            d.addCallback(self._loginByHash, username, hash, salt )
        elif password:
            log.debug("login handled by _loginByClear")
            d.addCallback(self._loginByClear, username, password  )
        elif key:
            log.debug("login handled by _loginByKey")
            d.addCallback(self._loginByKey, username, key)
        elif cookie:
            log.debug("login handled by _loginByCookie")
            d.addCallback(self._loginByCookie, username, cookie)
        else:
            raise Exception("Exhausted authentication mechanisms")
        
                            
        d.addCallback(self._LoginPassedBind, protocol)
        return d
    
    def mappedResultQuery(self, txn, query, args = ()):
        """
            TODO - Move this to util or similar
            Acts like runQuery except it convers the results into a list of dict's
        """
        txn.execute(query, args)
        results = txn.fetchall()
        columns = [d[0] for d in txn.description]
        return [dict(zip(columns, r)) for r in results]
        
    
    def _loginFailure(self, result):
        log.debug("Login failed because of %s " % result)
        
    def _LoginPassedBind(self, userObj, protocol):
        log.error("Bind was correctly called")
        userObj.setProtocol(protocol)
        return userObj
    
    def _loginByClear(self, results, username, password):
        """
           username - Valid username in authentication backstore
           password - A password that after being hashed to a sha1 digest is valid
        """
        
        if len(results) <= 0:
            raise LoginFailed("No such user")
        
        result = results[0]
        
        if sha1(password).hexdigest() != result["password"]:
            raise LoginFailed("Password discrepancy")
            
        userTypeMethod = "get%s" % result["type"]
        
        if hasattr(self, userTypeMethod):
            action = getattr(self, userTypeMethod)
            return action(result)
        else:
            raise LoginFailed("Unknown user-type %s" % result)
    
    def _loginByKey(self, result, username, key):
        pass
    
    def _loginByCookie(self, result, cookie):
        pass
    
    def _loginByHash(self, result, username, hash, salt ):        
        pass
        
    def _login_tuple(self, result, authPair):
        pass
    
        
    